#include <err.h>
#include <stdint.h>
#include <stdio.h>

#include <sys/utsname.h>

#include "args.h"
#include "cpuinfo.h"

#include "../metaargs_helper.h"
#include "../util.h"

const char *usage_uname =
    "Usage: %s [OPTION]...\n"
    "Print certain system information.  With no OPTION, same as -s.\n"
    "\n"
    "  -a, --all                print all information, in the following "
    "order,\n"
    "                             except omit -p and -i if unknown:\n"
    "  -s, --kernel-name        print the kernel name\n"
    "  -n, --nodename           print the network node hostname\n"
    "  -r, --kernel-release     print the kernel release\n"
    "  -v, --kernel-version     print the kernel version\n"
    "  -m, --machine            print the machine hardware name\n"
    "  -p, --processor          print the processor type (non-portable)\n"
    "  -i, --hardware-platform  print the hardware platform (non-portable)\n"
    "  -o, --operating-system   print the operating system\n"
    "      --help               display this help and exit\n"
    "      --version            output version information and exit\n";

const char *usage_arch =
    "Usage: %s [OPTION]...\n"
    "Print machine architecture.\n"
    "\n"
    "      --help     display this help and exit\n"
    "      --version  output version information and exit\n";

static void display_uname(const struct utsname	 *utsname,
			  const ArgsConfig_uname *cfg);

int
main(int argc, char *argv[])
{
	const char *usage_str;

	ArgsConfig_uname config = { 0 };

	if (STREQ(__progname, "arch")) {
		usage_str = usage_arch;
		METAARGS_PARSE(arch, config_arch, argc, argv);

		config.arg_machine.is_set = !0;
	} else {
		config = parse_args_uname(argc, argv);

		usage_str = usage_uname;
		METAARGS_HELP_VERSION(config);
	}

	struct utsname utsname;

	if (uname(&utsname) == -1) err(!0, "uname(2)");

	display_uname(&utsname, &config);

	return 0;
}

static uint8_t elements_printed = 0;

static void
print_element(const char *str)
{
	if (elements_printed++) putchar(' ');
	(void)fputs(str, stdout);
}

static void
display_uname(const struct utsname *utsname, const ArgsConfig_uname *cfg)
{
	if (cfg->arg_all.is_set || cfg->arg_kernel_name.is_set
	    || cfg->arg_sysname.is_set)
		print_element(utsname->sysname);

	if (cfg->arg_all.is_set || cfg->arg_nodename.is_set)
		print_element(utsname->nodename);

	if (cfg->arg_all.is_set || cfg->arg_release.is_set
	    || cfg->arg_kernel_release.is_set)
		print_element(utsname->release);

	if (cfg->arg_all.is_set || cfg->arg_kernel_version.is_set)
		print_element(utsname->version);

	if (cfg->arg_all.is_set || cfg->arg_machine.is_set)
		print_element(utsname->machine);

	if (cfg->arg_all.is_set || cfg->arg_processor.is_set) {
		const char *str = get_processor();
		if (!!str)
			print_element(str);
		else if (cfg->arg_processor.is_set)
			print_element("unknown");
	}

	if (cfg->arg_all.is_set || cfg->arg_platform.is_set) {
		const char *str = get_hw_platform();
		if (!!str)
			print_element(str);
		else if (cfg->arg_platform.is_set)
			print_element("unknown");
	}

	if (cfg->arg_os.is_set)
		print_element(
#if defined(__linux__)
#	if defined(NONGNU)                                                   \
	    || !((defined(__GNUC__) || defined(__GLIBC__))                    \
		 && !defined(__llvm__) && !defined(__INTEL_COMPILER))
		    "Non"
#	endif
		    "GNU/Linux"
#elif defined(__FreeBSD__)
		    "FreeBSD"
#elif defined(__OpenBSD__)
		    "OpenBSD"
#elif defined(_WIN32)
		    "Windows???"
#else
		    "Unknown operating system"
#endif
		);

	/*
	 * if nothin were printed (uname was launched without args)
	 * print sysname
	 */
	if (!elements_printed)
		print_element(utsname->sysname);

	putchar('\n');
}
